package com.tyx.chargingpile.service;

import com.tyx.chargingpile.util.Util;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.ResourceUtils;

import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 报文
 */
public class MessageService {

    /**
     * 组合报文数据
     * @param dataMap 数据域数据属性的键值对
     * @param xmlFileName 配置文件的路径
     * @return 报文数据
     * @throws Exception
     */
    public static byte[] getMessage(Map<String,String> dataMap,String xmlFileName) throws Exception {
        // 创建SAXReader对象
        SAXReader reader = new SAXReader();
        ClassPathResource classPathResource = new ClassPathResource(xmlFileName);
        InputStream inputStream =classPathResource.getInputStream();
        Document document = reader.read(inputStream);
        //Document document = reader.read(ResourceUtils.getFile("classpath:" + xmlFileName));

        // 获取XML文件根节点
        Element root = document.getRootElement();
        // 获取根节点以下的子元素
        List<Element> list = root.elements();
        // 要将数据域每个属性的byte[]集合起来
        List<byte[]> dataFieldAttributeList = new ArrayList<>();
        // 数据域总长度
        int dataFieldLen = 0;
        // 指令控制位
        byte[] commandControl = new byte[1];
        for (Element item : list) {
            String name = item.attributeValue("name");
            if (name.equals("commandControl")){
                String commandControlStr = item.attributeValue("value");
                commandControl = Util.String2BCD(commandControlStr);
                continue;
            }
            String value = dataMap.get(name);
            String type = item.attributeValue("type");
            // 数据域属性
            byte[] dataFieldAttribute;
                switch (type){
                case "bcd":
                case "hex":
                    dataFieldAttribute = Util.String2BCD(value);
                    break;
                case "asc":
                    dataFieldAttribute = Util.String2Byte(value);
                    break;
                case "field":
                    dataFieldAttribute = Util.String2BCD(value);
                    break;
                default:
                    throw new Exception("未知数据类型--" + type);
            }
            dataFieldAttributeList.add(dataFieldAttribute);
            dataFieldLen += dataFieldAttribute.length;
        }
        // 数据域
        byte[] dataField = new byte[dataFieldLen];
        int currentLength = 0;
        // 将数据域的每个属性合并起来
        for (byte[] b : dataFieldAttributeList) {
            System.arraycopy(b,0,dataField,currentLength,b.length);
            currentLength += b.length;
        }
        // 报文头
        byte[] start = {0x68};
        // 数据域数据长度
        byte[] dataLen = new byte[2];
        // 取长度转为hex值，取后两字节
        byte[] len = Util.IntToHex(dataField.length);
        dataLen[0] = len[2];
        dataLen[1] = len[3];

        //合并计算校验位所需要的数据
        String startStr = Util.BCD2String(start);
        String commandControlStr = Util.BCD2String(commandControl);
        String dataLenStr = Util.BCD2String(dataLen);
        String dataFieldStr = Util.BCD2String(dataField);
        StringBuffer sb = new StringBuffer();
        sb.append(startStr).append(commandControlStr).append(dataLenStr).append(dataFieldStr);
        byte[] calcCheckBitData = Util.String2BCD(sb.toString());
        // 获取校验位
        byte[] checkBit = calcCheckBit(calcCheckBitData);

        sb.append(Util.BCD2String(checkBit));
        return Util.String2BCD(sb.toString());
    }

    /**
     * 解析报文
     * @param messageBytes 完整的报文数据
     * @param xmlFileName 配置文件路径
     * @return 返回数据域的键值对
     * @throws Exception
     */
    public static Map<String,String> analysisMessage(byte[] messageBytes,String xmlFileName) throws Exception {
        byte[] start = new byte[1]; // 起始字节
        byte[] commandControl = new byte[1]; // 控制指令
        byte[] dataFieldLen = new byte[2]; // 数据域长度
        byte[] dataField; // 数据域
        byte[] checkBit = new byte[1]; // 校验位

        // 根据天涯行充电桩通信协议，报文的长度大于5
        if (messageBytes.length < 5){
            throw new Exception("接受到的错误的报文，报文的长度小于5，不符合天涯行充电桩通信协议");
        }

        int len = 0;
        // 截取起始字节
        System.arraycopy(messageBytes,len,start,0,start.length);
        len += start.length;
        // 截取控制指令
        System.arraycopy(messageBytes,len,commandControl,0,commandControl.length);
        len += commandControl.length;
        // 截取数据域长度
        System.arraycopy(messageBytes,len,dataFieldLen,0,dataFieldLen.length);
        // 将数据域长度转为整形
        int iDataFieldLen = Util.HexToint(dataFieldLen,dataFieldLen.length);
        dataField = new byte[iDataFieldLen];
        len += dataFieldLen.length;
        // 截取数据域
        System.arraycopy(messageBytes,len,dataField,0,dataField.length);
        len += dataField.length;
        // 截取校验位
        System.arraycopy(messageBytes,len,checkBit,0,checkBit.length);

        /************解析数据域***************/
        // 创建SAXReader对象
        SAXReader reader = new SAXReader();
        ClassPathResource classPathResource = new ClassPathResource(xmlFileName);
        InputStream inputStream =classPathResource.getInputStream();
        Document document = reader.read(inputStream);
        //Document document = reader.read(ResourceUtils.getFile("classpath:" + xmlFileName));
        // 获取XML文件根节点
        Element root = document.getRootElement();
        // 获取根节点以下的子元素
        List<Element> list = root.elements();
        int currentLen = 0;
        Map<String,String> map = new HashMap<>();
        int msgLen = 0;
        for (Element item : list) {
            String attributeName = item.attributeValue("name");
            if (attributeName.equals("commandControl")){
                // 跳过控制指令
                continue;
            }
            // 循环把各个的数据域相加，得出报文配置文件数据域的总长度
            // 属性长度
            String attributeLen = item.attributeValue("length");
            // 转化为int
            int iAttributeLen = Integer.parseInt(attributeLen);
            msgLen += iAttributeLen;
        }
        if (msgLen != iDataFieldLen ){
            throw new Exception(xmlFileName+"数据域长度跟获取的报文长度不一致");
        }
        for (Element item : list) {
            String attributeName = item.attributeValue("name");
            if (attributeName.equals("commandControl")){
                // 跳过控制指令
                continue;
            }
            // 属性长度
            String attributeLen = item.attributeValue("length");
            // 属性类型
            String attributeType = item.attributeValue("type");
            // 转化为int
            int iAttributeLen = Integer.parseInt(attributeLen);
            // 定义数据属性
            byte[] attributeBytes = new byte[iAttributeLen];
            // 截取属性数据
            System.arraycopy(dataField,currentLen,attributeBytes,0,iAttributeLen);
            currentLen += iAttributeLen;
            // 根据属性类型，将数据属性转化为String
            String attributeStr = "";
            switch (attributeType){
                case "bcd":
                case "hex":
                    attributeStr = Util.BCD2String(attributeBytes);
                    break;
                case "asc":
                    attributeStr = Util.Byte2String(attributeBytes);
                    break;
                case "field":
                    // 这个属性就代表着数据域
                    attributeStr = Util.BCD2String(dataField);
                    break;
                default:
                    throw new Exception("未知数据类型--" + attributeType);
            }
            map.put(attributeName,attributeStr);
        }
        return map;
    }


    /**
     * 获取到报文的控制指令
     * @param bytes 整个报文的数据
     * @return 控制指令的字符串
     */
    public static String getCommandControl(byte[] bytes){
        byte[] commandControl = new byte[1];
        // 截取控制指令
        System.arraycopy(bytes,1,commandControl,0,commandControl.length);
        String commandControlStr = Util.BCD2String(commandControl);
        return commandControlStr;
    }

    /**
     * 校验位
     * 报文数据的字节（从0x68开始）相累加和对256取模的余数,返回1字节
     * @param bytes 报文头到数据域的数据合
     * @return 计算出来的校验位
     */
    public static byte[] calcCheckBit(byte[] bytes){
        int total = 0;
        for (byte b : bytes) {
            byte[] hex = new byte[1];
            hex[0] = b;
            int value = Util.HexToint(hex,1);
            total += value;
        }
        int moc = total % 256;
        byte[] mocBytes = Util.IntToHex(moc);
        byte[] result = new byte[1];
        result[0] = mocBytes[3];
        return result;
    }

    /**
     * 检查校验位
     * @param bytes 整个报文的数据
     * @return true -- 校验位正确，false -- 校验位异常
     */
    public static boolean isCheckBit(byte[] bytes){
        byte[] indexByte = new byte[1];
        System.arraycopy(bytes,0,indexByte,0,1);
        String indexStr = Util.BCD2String(indexByte);
        if(!indexStr.equals("68")){
            return false;
        }
        // 截取到校验位
        byte[] destCheckBit = new byte[1];
        System.arraycopy(bytes,bytes.length-1,destCheckBit,0,destCheckBit.length);
        // 截取到计算校验位的数据
        byte[] checkBitData = new byte[bytes.length-1];
        System.arraycopy(bytes,0,checkBitData,0,checkBitData.length);
        // 根据数据自己计算校验位
        byte[] srcCheckBit = calcCheckBit(checkBitData);
        String destCheckBitStr = Util.BCD2String(destCheckBit);
        String srcCheckBitStr = Util.BCD2String(srcCheckBit);
        // 根据计算得来的校验位跟截取到的校验位比较
        if (destCheckBitStr.equals(srcCheckBitStr)){
            return true;
        }
        return false;
    }
}
